package com.lincoln;

import com.lincoln.framework.bean.AbstractProtocol;
import com.lincoln.framework.bean.ProtocolContext;
import com.lincoln.framework.exception.ErrorFormatException;
import com.lincoln.framework.exception.ExecuteException;
import com.lincoln.framework.exception.ExecuteParamWrongException;
import com.lincoln.framework.exception.NettyException;
import com.lincoln.framework.factories.ProtocolFactory;
import com.lincoln.framework.factories.ProtocolFactoryBean;
import com.lincoln.framework.interfaces.BaseDeviceInfo;
import com.lincoln.framework.interfaces.BaseExecute;
import com.lincoln.util.ByteUtil;
import com.lincoln.util.PayloadUtil;
import lombok.extern.slf4j.Slf4j;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.UUID;

@Slf4j
public class WebSocketServer {
    protected String clientId = null;
    protected int deviceType = -1;


    //建立连接成功调用
    public void onOpen(Session session) throws Exception {
        if (clientId == null) {
            clientId = UUID.randomUUID().toString();
            BaseDeviceInfo deviceInfo = WebSocketConfig.getWebSocketConfig().getDeviceInfo().newInstance();
            deviceInfo.setClientId(clientId);
            RemoteEndpoint.Async async = session.getAsyncRemote();
            InetSocketAddress addr = (InetSocketAddress) getFieldInstance(async, "base#socketWrapper#socket#sc#remoteAddress");
            deviceInfo.setIp(addr.getHostName());
            deviceInfo.setPort(addr.getPort());
            ProtocolContext.addNoLogin(deviceInfo);
            ProtocolContext.setDevice(deviceInfo);
            log.info("未认证的新客户端通过websocket进入!clientId=[" + clientId + "]");
        }
    }

    //关闭连接时调用
    public void onClose(Session session) {
        if (null != clientId && !"".equals(clientId) && null != ProtocolContext.getDevice()) {
            BaseDeviceInfo deviceInfo = ProtocolContext.getDevice();
            if (deviceInfo.isLogin()) {
                log.info("有认证的客户端退出!clientId=[" + clientId + "],primaryKey=[" + deviceInfo.getPrimaryKey() + "]");
                deviceInfo.logout();
                ProtocolContext.removeClient(deviceInfo.getDeviceType(), clientId);
            } else {
                log.info("有未认证的客户端退出!clientId=[" + clientId + "]");
                deviceInfo.logout();
                ProtocolContext.removeNoLogin(clientId);
            }
        }
    }

    //收到客户端信息
    public void onMessage(byte[] data, Session session) throws IOException {
        try {

            AbstractProtocol protocol = new AbstractProtocol(data);
            if (!protocol.verify()) {
                throw new ErrorFormatException("收到的消息格式错误!");
            }
            BaseDeviceInfo deviceInfo;
            if (deviceType == -1) {
                deviceInfo = ProtocolContext.getDevice();
            } else {
                deviceInfo = ProtocolContext.getClient(deviceType, clientId);
            }

            deviceInfo.setMsgSender(session);
            RemoteEndpoint.Async async = session.getAsyncRemote();
            InetSocketAddress addr = (InetSocketAddress) getFieldInstance(async, "base#socketWrapper#socket#sc#remoteAddress");
            deviceInfo.setIp(addr.getHostName());
            deviceInfo.setPort(addr.getPort());

            //请求次数+1
            deviceInfo.getAttributes().put(BaseDeviceInfo.REQUEST_TIME, (int) (deviceInfo.getAttributes().get(BaseDeviceInfo.NO_LOGIN_REQUEST_TIME) == null ? 0 : deviceInfo.getAttributes().get(BaseDeviceInfo.REQUEST_TIME)) + 1);
            //获取对应协议
            ProtocolFactoryBean protocolFactory = ProtocolFactory.getProtocol(deviceInfo.getDeviceType(), deviceInfo.getProtocolType());

            if (!deviceInfo.isLogin()) {
                //未登录请求次数+1
                deviceInfo.getAttributes().put(BaseDeviceInfo.NO_LOGIN_REQUEST_TIME, (int) (deviceInfo.getAttributes().get(BaseDeviceInfo.NO_LOGIN_REQUEST_TIME) == null ? 0 : deviceInfo.getAttributes().get(BaseDeviceInfo.NO_LOGIN_REQUEST_TIME)) + 1);
                BaseExecute execute = ProtocolFactory.defaultExecute;
                if (protocol.getCmd()[0] == 0) {
                    execute.execute(protocol);
                    byte[] payload = protocol.getPayload();
                    byte[][] params = PayloadUtil.resolvePayload(payload);
                    int deviceType = ByteUtil.byte2int(params[0]);
                    this.deviceType = deviceType;
                } else {
                    throw new NettyException("未找到对应的执行器");
                }
            } else {
                // TODO: 2018/11/30 此节点要增加灵活的执行器,决定是否要继续向下执行

                BaseExecute execute = null;
                if (protocolFactory != null) {
                    execute = protocolFactory.getExecute(protocol.getCmdText());
                }
                if (execute == null) {
                    // TODO: 2018/11/30 此节点要增加灵活的执行器,加入一些默认的执行器
                    throw new NettyException("未找到对应的执行器");
                } else {
                    execute.execute(protocol);
                }
            }
        } catch (ExecuteParamWrongException e) {
            log.error("执行器检测到参数错误", e);
        } catch (ExecuteException e) {
            log.error("执行出现异常:", e);
        } catch (Exception e) {
            log.error("异常:", e);
        }
    }

    //错误时调用
    public void onError(Session session, Throwable throwable) {
        if (throwable instanceof IOException) {
            //do nothing
        } else {
            log.error("", throwable);
        }
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private static Object getFieldInstance(Object obj, String fieldPath) {
        String fields[] = fieldPath.split("#");
        for (String field : fields) {
            obj = getField(obj, obj.getClass(), field);
            if (obj == null) {
                return null;
            }
        }

        return obj;
    }

    private static Object getField(Object obj, Class<?> clazz, String fieldName) {
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                Field field;
                field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            } catch (Exception e) {
            }
        }

        return null;
    }
}
